package com.geek.gkpush.framework.retry.model;

import com.geek.gkpush.framework.retry.RetryMsgHandler;
import com.geek.gkpush.framework.retry.enums.ExecStatusEnum;
import com.geek.gkpush.util.enums.status.StatusEnum;
import com.geek.gkpush.util.id.BizUtils;
import com.geek.gkpush.util.hash.HashUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.Date;

/**
 * <h1>重试任务插入数据模型对象。</h1><br/>
 *
 * <h1>1、唯一性：bizValue + bizType</h1>
 * <h1>2、bizId 用于串联一堆任务用的，没有业务逻辑，只是给查询数据库归类看的。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2023/10/3 09:10
 */
@Setter
@Getter
@ToString(callSuper = true)
public class RetryInsertModel implements Serializable {

    private static final long serialVersionUID = 2875809104974269594L;

    /** <h2>业务ID，bizId 用于串联一堆任务用的，没有业务逻辑，只是给查询数据库归类看的</h2> **/
    private String bizId;

    /** <h2>业务ID，唯一性：bizValue + bizType</h2> **/
    private String bizValue;
    /** <h2>业务类型.通过 bizTypeClass 需要计算出 bizType，而 bizType 以后就是 bean 的名称</h2> **/
    private Class<? extends RetryMsgHandler> bizTypeClass;
    /** <h2>业务ID，通过 bizTypeClass 需要计算出 bizType</h2> **/
    private String bizType;

    /** <h2>重试任务首次执行时向后偏移当前时间多少秒，也就是未来多少秒后第一次执行</h2> **/
    private Integer firstDelaySeconds;

    private RetryInsertModel() {
    }

    public RetryInsertModel(String bizId,
                            String bizValue,
                            Class<? extends RetryMsgHandler> bizTypeClass,
                            Integer firstDelaySeconds) {
        this.bizId = bizId;
        this.bizValue = bizValue;
        this.bizTypeClass = bizTypeClass;
        this.bizType = StringUtils.uncapitalize(bizTypeClass.getSimpleName());
        this.firstDelaySeconds = firstDelaySeconds;
    }

    /**
     * <h2>将 {@linkplain RetryInsertModel} + {@linkplain RetryConfigModel} 转换为 {@linkplain RetryMsgDTO} 对象，便于入库处理。</h2>
     *
     * @param: retryConfig
     * @return: com.geek.gkpush.framework.retry.model.RetryMsgDTO
     */
    public RetryMsgDTO convert2RetryMsgDTO(RetryConfigModel retryConfig) {
        RetryMsgDTO retryMsgDTO = new RetryMsgDTO();
        retryMsgDTO.setId(BizUtils.getRetryMsgId());
        retryMsgDTO.setBizId(getBizId());

        retryMsgDTO.setBizValue(getBizValue());
        retryMsgDTO.setBizType(getBizType());
        retryMsgDTO.setHashCode((long) HashUtils.hash(getBizType() + getBizId()));

        retryMsgDTO.setFirstExecTime(calcFirstExecTime(getFirstDelaySeconds(), retryConfig.getFirstDelaySeconds()));
        retryMsgDTO.setLastExecTime(retryMsgDTO.getFirstExecTime());
        retryMsgDTO.setNextExecTime(retryMsgDTO.getFirstExecTime());

        retryMsgDTO.setExecTimes(0);
        retryMsgDTO.setMaxRetryTimes(retryConfig.getMaxRetryTimes());

        retryMsgDTO.setExecStatus(ExecStatusEnum.INIT.getCode());
        retryMsgDTO.setExecResult(ExecStatusEnum.INIT.getMsg());
        retryMsgDTO.setPseudo(retryMsgDTO.buildPseudo());
        retryMsgDTO.setStatus(StatusEnum.V.getCode());

        return retryMsgDTO;
    }

    /**
     * <h2>reqFirstDelaySeconds 代码指定了就用代码的，否则就用 cfgFirstDelaySeconds 配置指定的。</h2>
     *
     * @param: reqFirstDelaySeconds
     * @param: cfgFirstDelaySeconds
     * @return: java.util.Date
     */
    private Date calcFirstExecTime(Integer reqFirstDelaySeconds, Integer cfgFirstDelaySeconds) {
        Integer calcSeconds = reqFirstDelaySeconds == null ? reqFirstDelaySeconds : cfgFirstDelaySeconds;

        // 既没有手动指定，也没有配置，那么就用当前时间
        if (calcSeconds == null) {
            return new Date();
        }

        // 偏移时间，并返回偏移后的时间对象
        return DateUtils.addSeconds(new Date(), calcSeconds);
    }
}